package lt.mii.optimum.components;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import lt.mii.optimum.Start;

/**
 * Class represents one lesson. <br>
 * A smallest part of the week.
 * 
 * @author dstrukov
 * 
 */
public class Lesson implements Comparable<Lesson> {

	/**
	 * Maximum Students per Lesson Constraint. Restricted by places in class
	 * room
	 */

	static int MAX_STUDENTS_PER_LESSON = 32;

	/**
	 * Minimum Students per lesson. Restricted by User. Default = 12
	 */
	static int MIN_STUDENTS_PER_LESSON = 5;

	/**
	 * Flow that lesson belongs to
	 */
	private Flow $flow;

	private Long $id;

	/**
	 * Students assigned to the lesson.<br>
	 * A subset of Flow.Students.
	 */
	private Set<Student> $students;

	/**
	 * Subject of the lesson
	 */
	private Subject $subject;

	/**
	 * teacher teaching the lesson
	 */
	private Teacher $teacher;

	/**
	 * Error message
	 */
	private String compilationMessage = "idle";

	/**
	 * Set of classes that are thought during the lesson
	 */
	private Set<Group> $groups = new HashSet<Group>();



	/**
	 * Default constructor <br>
	 * Does nothing.
	 */
	public Lesson() {

	}



	/**
	 * Constructor <br>
	 * Creates an empty lesson and assigns it to a flow
	 * 
	 * @param flow
	 *            to assign to.
	 */
	public Lesson(Flow flow) {
		$id = new Long(flow.getLessons().size() + 1);
		$students = new HashSet<Student>();
		$flow = flow;

	}



	/**
	 * Adds Student to Lesson (also adds it to a flow lesson belongs to)
	 * 
	 * @param student
	 *            to add.
	 * @return <code>false</code> on failure
	 */
	boolean addStudent(Student student) {
		if ($flow.addStudent(student)) {
			$students.add(student);
			student.addLesson(this);
			$groups.add(student.getGroup());

			assert $groups.contains(student.getGroup());
			assert student.$lessons.contains(this);
			assert $students.contains(student);
			assert $flow.getPersonList().contains(student);
			return true;
		}

		return false;

	}



	/**
	 * Collects, if possible, a group of students for a given {@link Teacher}
	 * <br>
	 * <br>
	 * <code>subject != null</code>
	 * 
	 * @param teach -
	 *            Teacher of the group
	 * @return Set of Students or an Empty Set if amount of students is
	 *         insufficient.
	 */
	private Set<Student> collectStudentGroup(Teacher teach) {
		assert $subject != null;

		Group lock = null;
		int levelLock = 0;

		Iterator<Student> it = Start.data.getStudents().iterator();

		Set<Student> candidates = new HashSet<Student>();
		Set<Group> set = teach.getGroups(this.$subject);

		while (it.hasNext()) {
			Student stud = it.next();

			if (stud.canStudy(this)) {

				if (set.contains(stud.getGroup())) {

					if (levelLock == 0)
						levelLock = stud.getGroup().getLevel();

					if (levelLock != 0
							&& stud.getGroup().getLevel() != levelLock)
						continue;

					if ($subject.getPriority() == 0 && lock == null)
						lock = stud.getGroup();

					if (lock != null && !stud.getGroup().equals(lock))
						continue;

					Set<Student> temp = stud.getGroup().exctractStudents(this);

					if (candidates.size() + temp.size() > MAX_STUDENTS_PER_LESSON) {

						setCompilationMessage("Klass Overflow: student count >= "
								+ MAX_STUDENTS_PER_LESSON);
						break;
					} else {
						candidates.addAll(temp);
					}

				}
			}

		}

		// if (candidates.size() <= MIN_STUDENTS_PER_LESSON)
		// candidates.clear();
		// if (candidates.isEmpty()) {
		// compilation(teach);
		//
		// }

		if (candidates.size() < MIN_STUDENTS_PER_LESSON)
			candidates.clear();
		return candidates;
	}



	/**
	 * Searches for a reason why collection of the group failed.
	 * 
	 * @param teach
	 *            er for who group collection failed
	 */
	private void compilation(Teacher teach) {
		Iterator<Student> it;
		boolean flag = false;
		it = Start.data.getStudents().iterator();
		while (it.hasNext()) {
			Student stud = it.next();
			if (stud.needsToStudy(this.getSubject())) {
				flag = true;
			}
		}

		if (!flag) {
			setCompilationMessage("Students dont need to study this subject any more: "
					+ this.getSubject());
		} else {
			flag = false;
			it = Start.data.getStudents().iterator();
			while (it.hasNext()) {
				Student stud = it.next();
				if (stud.needsToStudy(this.getSubject())) {
					if (!this.getFlow().getPersonList().contains(stud)) {
						flag = true;
					}
				}
			}

			if (!flag) {
				setCompilationMessage("Students DO need to study this subject, but all of them are busy in the Flow: "
						+ this.getSubject());
			} else {
				flag = false;
				it = Start.data.getStudents().iterator();
				Group klass = null;
				while (it.hasNext()) {
					Student stud = it.next();
					if (stud.needsToStudy(this.getSubject())) {
						if (!this.getFlow().getPersonList().contains(stud)) {
							Set<Group> set = teach.getGroups(this.getSubject());
							if (set == null)
								continue;
							if (set.contains(stud.getGroup())) {
								flag = true;
							} else {
								klass = stud.getGroup();
							}
						}
					}
				}
				if (!flag) {
					setCompilationMessage("Students DO need to study this subject, but Teacher does not teach this class: ["
							+ klass
							+ "] subject ["
							+ this.getSubject()
							+ "] Teacher [" + teach + "]");
				}
			}
		}
	}



	/**
	 * Tries to compile a Lesson for a given Subject.
	 * 
	 * @param subject
	 * @return
	 */
	public boolean compile(Subject subject) {
		$subject = subject;
		assert Start.data.getSubjects().contains(subject);

		Set<Teacher> teachers;
		Set<Student> students;
		if (!(teachers = findTeacher()).isEmpty()) {
			setCompilationMessage("teachers found");
			Iterator<Teacher> tit = teachers.iterator();
			while (tit.hasNext()) {
				Teacher teach = tit.next();

				if (!(students = collectStudentGroup(teach)).isEmpty()) {
					setCompilationMessage("Students found");
					setTeacher(teach);
					Iterator<Student> it = students.iterator();
					while (it.hasNext()) {
						addStudent(it.next());
					}

					assert $flow.getLessons().contains(this);
					assert $flow.getPersonList().contains($teacher);
					refreshClassList();
					System.out.println("Lesson created: Subject:"
							+ $subject.getSubjCode() + "  Teacher: "
							+ $teacher.getName() + " Student count : "
							+ $students.size() + " from  klasses: "
							+ $groups.toString());
					return true;

				}
			}

		}

		return false;

	}



	/**
	 * Set error message
	 * 
	 * @param compilationMessage
	 */
	public void setCompilationMessage(String compilationMessage) {
		this.compilationMessage = compilationMessage;
	}



	/**
	 * Searches for teachers that can teach this lesson
	 * 
	 * @return Set of teachers that can teach this lesson
	 */
	public Set<Teacher> findTeacher() {

		Iterator<Teacher> it = Start.data.getTeachers().iterator();
		Set<Teacher> teachers = new HashSet<Teacher>();
		while (it.hasNext()) {
			Teacher teach = it.next();

			if (teach.canTeach(this)) {

				teachers.add(teach);
			}

		}

		if (teachers.isEmpty()) {
			testTeachers();
		}
		return teachers;
	}



	/**
	 * Searches for a reason why Teacher was not found.
	 */
	private void testTeachers() {
		Iterator<Teacher> itt = Start.data.getTeachers().iterator();

		boolean isTeacher = false;
		while (itt.hasNext()) {
			Teacher teach = itt.next();
			if (teach.getSubjects().contains(this.getSubject())) {
				if (teach.getHours(this.getSubject()) > 0)
					isTeacher = true;
			}
		}

		if (!isTeacher) {
			setCompilationMessage("There are no Teachers that can teach "
					+ this.getSubject());
		} else {
			isTeacher = false;

			itt = Start.data.getTeachers().iterator();
			while (itt.hasNext()) {
				Teacher teach = itt.next();
				if (teach.getSubjects().contains(this.getSubject())) {
					if (teach.getHours(this.getSubject()) > 0)
						if (!this.getFlow().getPersonList().contains(teach)) {
							isTeacher = true;
						}
				}
			}

			if (!isTeacher) {
				setCompilationMessage("All available teachers for this subject are busy. Subj = "
						+ this.getSubject());
			}

		}
	}



	public Flow getFlow() {
		return $flow;
	}



	public Set<Student> getStudents() {
		return $students;
	}



	public Subject getSubject() {
		return $subject;
	}



	public Teacher getTeacher() {
		return $teacher;
	}



	/**
	 * removes Student from Lesson and Flow
	 * 
	 * @param student
	 *            to remove
	 */
	void removeStudent(Student student) {
		if ($flow.removeStudent(student)) {
			$students.remove(student);
			student.removeLesson(this);

		}

		assert !$flow.getPersonList().contains(student);
		assert !student.$lessons.contains(this);
		assert !$students.contains(student);

	}



	void refreshClassList() {
		Iterator<Student> it = $students.iterator();
		while (it.hasNext()) {
			$groups.add(it.next().getGroup());
		}
	}



	/**
	 * Sets teacher for this lesson
	 * 
	 * @param teacher
	 *            to set
	 * 
	 * @return <code>true</code> on success
	 */
	public boolean setTeacher(Teacher teacher) {
		if ($flow.addTeacher(teacher)) {
			$teacher = teacher;
			teacher.addLesson(this);
			assert $flow.getPersonList().contains($teacher);
			assert $teacher.getLessons().contains(this);
			return true;
		}

		assert !$flow.getPersonList().contains($teacher);
		assert $teacher == null;
		return false;
	}



	@Override
	public String toString() {
		// TODO Auto-generated method stub
		String retStr = $subject.getSubjCode() + " " + $teacher.getName()
				+ "\n";
		Iterator<Student> it = $students.iterator();
		while (it.hasNext()) {
			retStr += it.next().getName() + "\n";
		}

		retStr += "------------------------------------------------------------------------ \n";
		return retStr;
	}



	public String getCompilationMessage() {
		return compilationMessage;
	}



	public Long getId() {
		return $id;
	}



	@Override
	public int compareTo(Lesson o) {
		if (this.$id == o.getId())
			return 0;
		if (this.$id > o.getId())
			return 1;
		assert this.$id < o.getId();

		return -1;
	}

}
